package cn.edu.pku.ss.situationawareperceiver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import cn.edu.pku.ss.situationawareperceiver.dbrecords.SRARecord;

import android.content.Context;
import android.os.Message;
import android.util.Log;

public class SituationReasoner {

	private static final String TAG = "SituationReasoner";
	private static final int timeoutPeriod = 25;	//Timeout Period
	
	private static HashMap<Integer, Situation> situations = new HashMap<Integer, Situation>();
	private List<Relation> incompatibles;
	private List<Relation> relations;

	private List<Integer> situationPool;
	private List<Integer> predicateSet;


	private SRARecord record;
	
	private List<Integer> deductiveNodes;
	private List<Integer> validateNodes;

	private static UIHandler mUiHandler;
	private static MainActivity mMainActivity;
	private boolean sraEnding;
	
	public SituationReasoner(UIHandler uiHandler,MainActivity mainActivity) {
		super();
		sraEnding = false;
		mUiHandler = uiHandler;
		mMainActivity = mainActivity;
		incompatibles = new ArrayList<Relation>();
		situationPool = new ArrayList<Integer>();
		predicateSet = new ArrayList<Integer>();
		deductiveNodes = new ArrayList<Integer>();
		validateNodes = new ArrayList<Integer>();
	}

	public HashMap<Integer, Situation> getAllSituation() {
		readSituations();
		return situations;
	}

	public static Situation getSituation(int id) {
		return situations.get(id);
	}

	public void startSRA(Context context) {
		readSituations();
		readRelations();
		record = new SRARecord();
		new Thread(new SRAThread()).start();
		logInfo("SRA is now started.");
	}
	
	private void readSituations() {
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();
			XMLReader reader = parser.getXMLReader();
			SituationHandler handler = new SituationHandler();
			reader.setContentHandler(handler);
			InputSource is = new InputSource(this.getClass()
					.getResourceAsStream("situation.xml"));
			reader.parse(is);
			situations = handler.getSituations();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void readRelations() {
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();
			XMLReader reader = parser.getXMLReader();
			RelationHandler handler = new RelationHandler();
			reader.setContentHandler(handler);
			InputSource is = new InputSource(this.getClass()
					.getResourceAsStream("situationStructure.xml"));
			reader.parse(is);
			relations = handler.getRelations();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void resetSituations() {
		Set<Integer> set = situations.keySet();
		Iterator<Integer> itr = set.iterator();
		while (itr.hasNext()) {
			int id = itr.next();
			Situation situation = situations.get(id);
			situation.setVerified(false);
			// Unactivate situation's tag on the main activity
//			activateTagOnMainActivity(false, id);
		}
	}
	
	public void resetSRA()
	{
		sraEnding = true;
	}
	
	class SRAThread implements Runnable {
		
		private int abductionCount;
		private String intermediateNodes;
		private long startTime;
		private long endTime;
		private int validateCountClipped;
		private int validateCountNoClip;
		
		private boolean checkTrigger() {
			situationPool.clear();
			Set<Integer> set = situations.keySet();
			Iterator<Integer> itr = set.iterator();
			while (itr.hasNext()) {
				int id = itr.next();
				Situation situation = situations.get(id);
				if (situation.isVerified() && situation.isExistence()) {
					sraEnding = false;
					abductionCount = 0;
					intermediateNodes = "";
					validateCountClipped = 0;
					validateCountNoClip = 0;
					startTime = System.currentTimeMillis();
					situationPool.add(id);
					record.setTriggerSituationId(id);
					record.setStartTime(mMainActivity.getUnifiedDateFormat().format(new Date()));
					logInfo("SRA is now triggerd by " + situation.getName()
							+ " ." + new Date().toString());
					return true;
				}
			}
			return false;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				if (checkTrigger()) {
					startReasoning();
					resetSituations();
				} else {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}

		private void startReasoning() {
			long begin = System.currentTimeMillis();
			while (!checkEnding()) {
				getDeductiveNodes();
				getValidateNodes();
				verifyValidateNodes();
				if (System.currentTimeMillis() - begin > timeoutPeriod * 1000)	//timeoutPeriod
					break;
			}
			
			Set<Integer> set = situations.keySet();
			Iterator<Integer> itS = set.iterator();
			while (itS.hasNext()) {
				int id = itS.next();
				Situation situation = situations.get(id);
				if(situation.isVerified() && situation.isExistence())
					validateCountNoClip++;
			}
				
			endTime = System.currentTimeMillis();
			String resultSituationId = "";
			Iterator<Integer> itr = situationPool.iterator();
			while (itr.hasNext())
				resultSituationId += itr.next() + " ";
			record.setResultSituationId(resultSituationId);
			record.setIntermediateNodes(intermediateNodes);
			record.setAbductionCount(abductionCount);
			record.setEndTime(mMainActivity.getUnifiedDateFormat().format(new Date()));
			record.setTimeCost((int)(endTime - startTime));
			record.setValidateCountClipped(validateCountClipped);
			record.setValidateCountNoClip(validateCountNoClip);
			
			DBAdapter dbAdapter = mMainActivity.getDBAdapter();
			if(dbAdapter != null){
				dbAdapter.insertSRARecord(record);
			}

		}

		private boolean hasFather(int id)
		{
			Iterator<Relation> itR = relations.iterator();
			while(itR.hasNext())
			{
				Relation relation = itR.next();
				if(relation.getPrevious() == id)
					return true;
			}
			return false;
		}
		
		private boolean checkEnding() {
			
			if(sraEnding == true)
				return true;
			
			boolean noFather = true;
			Iterator<Integer> itrSP = situationPool.iterator();
			while(itrSP.hasNext())
			{
				int situationID = itrSP.next();
				if(hasFather(situationID))
					noFather = false; 
			}
			if(noFather == true)
				return true;
			
			if (predicateSet.isEmpty())
				return false;
			else {
				Iterator<Integer> itr = predicateSet.iterator();
				while (itr.hasNext())
					if (!inList(itr.next(), situationPool))
						return false;
				return true;
			}
		}

		private boolean hasConflict(int one, int another) {
			Iterator<Relation> itr = incompatibles.iterator();
			while (itr.hasNext()) {
				Relation relation = itr.next();
				if ((relation.getPrevious() == one && relation.getNext() == another)
						|| (relation.getPrevious() == another && relation
								.getNext() == one))
					return true;
			}
			return false;
		}

		private int clipNodeSuccess(int deductive, int pos) {
			List<Integer> clipNodes = new ArrayList<Integer>();
			for (int i = 0; i < deductiveNodes.size(); i++) {
				int deductiveNode = deductiveNodes.get(i);
				if (hasRelation(deductiveNode, deductive)
						|| hasConflict(deductiveNode, deductive)) {
					deductiveNodes.remove(i);
					clipNodes.add(deductiveNode);
					i--;
				}
			}

			for (int j = 0; j < validateNodes.size(); j++) {
				int validateNode = validateNodes.get(j);
				if (hasRelation(validateNode, deductive)
						&& situations.get(validateNode).isTransparent()) {
					validateNodes.remove(j);
					j--;
					continue;
				}

				Iterator<Integer> itr = clipNodes.iterator();
				while (itr.hasNext()) {
					int clip = itr.next();
					if (hasRelation(validateNode, clip)) {
						validateNodes.remove(j);
						j--;
						break;
					}
				}
			}

			for (int k = 0; k < validateNodes.size(); k++) {
				int validateNode = validateNodes.get(k);
				Situation situation = situations.get(validateNode);
				if (!situation.isVerified())
					return k - 1;
			}
			return pos;
		}

		private int checkDeduction(int validate, int pos) {
			int deductiveNode = -1;
			Iterator<Integer> itr = deductiveNodes.iterator();
			while (itr.hasNext()) {
				deductiveNode = itr.next();
				Situation situation = situations.get(deductiveNode);
				logInfo("deductive situation " + situation.getName());
				if (!situation.isVerified())
				{
//					logInfo("deductive situation not verified" + situation.getName());
					break;
				}
			}
			if (deductiveNode == -1)
				return pos;

			if (!situations.get(validate).isTransparent()) {
				Iterator<Relation> it = relations.iterator();
				while (it.hasNext()) {
					Relation relation = it.next();
					if (relation.getNext() == deductiveNode
							&& !situations.get(relation.getPrevious())
									.isVerified())
						return pos;
				}
			}
			
			situations.get(deductiveNode).setVerified(true);
			situations.get(deductiveNode).setExistence(true);
			
			logInfo("New situation deducted: "+ situations.get(deductiveNode).getName());
			//activate the tag
			activateTagOnMainActivity(true, deductiveNode);
			abductionCount++;
			return clipNodeSuccess(deductiveNode, pos);
		}

		private boolean hasRelation(int validate, int deductive) {
			Iterator<Relation> itr = relations.iterator();
			while (itr.hasNext()) {
				Relation relation = itr.next();
				if (relation.getPrevious() == validate
						&& relation.getNext() == deductive)
					return true;
			}
			return false;
		}

		private int clipNodeFailure(int validate, int pos) {
			List<Integer> clipNodes = new ArrayList<Integer>();
			for (int i = 0; i < deductiveNodes.size(); i++) {
				int deductiveNode = deductiveNodes.get(i);
				if (hasRelation(validate, deductiveNode)) {
					deductiveNodes.remove(i);
					clipNodes.add(deductiveNode);
					i--;
				}
			}

			for (int j = 0; j < validateNodes.size(); j++) {
				int validateNode = validateNodes.get(j);
				Iterator<Integer> itr = clipNodes.iterator();
				while (itr.hasNext()) {
					int clip = itr.next();
					if (hasRelation(validateNode, clip)) {
						validateNodes.remove(j);
						j--;
						break;
					}
				}
			}

			for (int k = 0; k < validateNodes.size(); k++) {
				int validateNode = validateNodes.get(k);
				Situation situation = situations.get(validateNode);
				if (!situation.isVerified())
					return k - 1;
			}
			return pos;
		}

		private boolean endChildrenFinding(List<Integer> validateNodes) {
			Iterator<Integer> itr = validateNodes.iterator();
			while (itr.hasNext()) {
				int validateNode = itr.next();
				if (hasChildSituations(validateNode))
					return false;
			}
			return true;
		}

		private boolean hasChildSituations(int validateNode) {
			Iterator<Relation> itr = relations.iterator();
			while (itr.hasNext()) {
				Relation relation = itr.next();
				if (relation.getNext() == validateNode) {
					return true;
				}
			}
			return false;
		}

		private List<Integer> getAtomSituations(int validateNode) {
			List<Integer> atoms = new ArrayList<Integer>();
			atoms.add(validateNode);
			while (!endChildrenFinding(atoms)) {
				List<Integer> children = new ArrayList<Integer>();
				Iterator<Integer> itra = atoms.iterator();
				while (itra.hasNext()) {
					int node = itra.next();
					if (hasChildSituations(node)) {
						Iterator<Relation> itr = relations.iterator();
						while (itr.hasNext()) {
							Relation relation = itr.next();
							if (relation.getNext() == node
									&& situations.get(relation.getPrevious())
											.isTransparent()) {
								children.add(relation.getPrevious());
								break;
							} else if (relation.getNext() == node) {
								children.add(relation.getPrevious());
							}
						}
					} else
						children.add(node);
				}
				atoms.clear();
				atoms.addAll(children);
			}
			return atoms;
		}

		private boolean situationVerified(List<Integer> atoms) {
			Iterator<Integer> itr = atoms.iterator();
			while (itr.hasNext()) {
				int atom = itr.next();
				Situation situation = situations.get(atom);
				if (!situation.isVerified())
					return false;
			}
			return true;
		}

		private boolean situationExist(List<Integer> atoms) {
			Iterator<Integer> itr = atoms.iterator();
			while (itr.hasNext()) {
				int atom = itr.next();
				Situation situation = situations.get(atom);
				if (situation.isExistence()){
					/* Found a situation is verified and existed */
					logInfo("SRA.verifyValidateNodes():Found [" + situation.getName() +"] is verified and existed");
					return true;
				}
					
			}
			return false;
		}

		private void requestSituations(List<Integer> atoms) {
			Iterator<Integer> itr = atoms.iterator();
			while (itr.hasNext()) {
				int id = itr.next();
				Situation situation = situations.get(id);
				if (!situation.isVerified())
					ContextInterpreter.requestSituation(id);
			}
		}

		private void verifyValidateNodes() {
			for (int i = 0; i < validateNodes.size(); i++) {
				int id = validateNodes.get(i);
				List<Integer> atoms = getAtomSituations(id);
				requestSituations(atoms);

				int time = 50;
				while (time >= 0) {
					if (situationVerified(atoms)) {
						if (situationExist(atoms))
							i = checkDeduction(id, i);
						else
							i = clipNodeFailure(id, i);
						break;
					}

					try {
						time--;
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					if(sraEnding == true)
					{
						deductiveNodes.clear();
						validateNodes.clear();
						return;
					}
				}
				if (!situationVerified(atoms))
					i = clipNodeFailure(id, i);
			}

			Iterator<Integer> itrVN = validateNodes.iterator();
			while (itrVN.hasNext())
			{
				validateCountClipped++;
				intermediateNodes += itrVN.next() + " ";
			}
			intermediateNodes += ",";

			situationPool.clear();
			Iterator<Integer> itr = deductiveNodes.iterator();
			String logStr = "Deduced situaions in this roll: ";
			while (itr.hasNext()) {
				int deductiveNodeID = itr.next();
				validateCountClipped++;
				situationPool.add(deductiveNodeID);
				intermediateNodes += deductiveNodeID + " ";
				logStr += " " + situations.get(deductiveNodeID).getName();
				
				// call main activity to show a progress
				
				ArrayList<Integer> exsList = new ArrayList<Integer>();
				if (!situations.get(deductiveNodeID).isTransparent()) {
					exsList.add(deductiveNodeID);
				} else {
					Iterator<Relation> reItr = relations.iterator();
					while (reItr.hasNext()) {
						Relation relation = reItr.next();
						if (relation.getPrevious() == deductiveNodeID) {
							exsList.add(relation.getNext());
							break;
						}
					}
					if (0 == exsList.size()) {
						Log.e(TAG,
								"ERROR: can't find the father node of transparent node: "
										+ deductiveNodeID);
					}
				}
				Iterator<Relation> exsIt = relations.iterator();
				while (exsIt.hasNext()) {
					Relation relation = exsIt.next();
					if (relation.getNext() == deductiveNodeID)
						exsList.add(relation.getPrevious());
				}
				showAbductiveProcess(exsList);
				// end of call
			}
			if (deductiveNodes.size() != 0) {
				logInfo(logStr);
			}

			intermediateNodes += ";";

			deductiveNodes.clear();
			validateNodes.clear();
		}

		private void getValidateNodes() {
			Iterator<Integer> itrDN = deductiveNodes.iterator();
			while (itrDN.hasNext()) {
				int deductive = itrDN.next();
				Iterator<Relation> itrR = relations.iterator();
				while (itrR.hasNext()) {
					Relation relation = itrR.next();
					if (relation.getNext() == deductive
							&& !inList(relation.getPrevious(), situationPool)
							&& !inList(relation.getPrevious(), validateNodes))
						validateNodes.add(relation.getPrevious());
				}
			}
		}

		private boolean inList(int element, List<Integer> list) {
			Iterator<Integer> itr = list.iterator();
			while (itr.hasNext()) {
				int i = itr.next();
				if (element == i)
					return true;
			}
			return false;
		}

		private void getDeductiveNodes() {
			Iterator<Integer> itrSP = situationPool.iterator();
			while (itrSP.hasNext()) {
				int element = itrSP.next();
				Iterator<Relation> itrR = relations.iterator();
				while (itrR.hasNext()) {
					Relation relation = itrR.next();
					if (relation.getPrevious() == element)
						deductiveNodes.add(relation.getNext());
				}
			}
			removeDuplicate(deductiveNodes);
			sortDeductiveNodes();
		}

		private void removeDuplicate(List<Integer> list) {
			HashSet<Integer> h = new HashSet<Integer>(list);
			list.clear();
			list.addAll(h);
		}

		private void sortDeductiveNodes() {
			DeductiveComparator comparator = new DeductiveComparator();
			Collections.sort(deductiveNodes, comparator);
		}

		private long getRC(int deductive) {
			long rc = 0;
			Iterator<Relation> itr = relations.iterator();
			while (itr.hasNext()) {
				Relation relation = itr.next();
				if (relation.getNext() == deductive)
					rc += situations.get(relation.getPrevious()).getRc();
			}
			return rc;
		}

		class DeductiveComparator implements Comparator<Integer> {
			@Override
			public int compare(Integer lhs, Integer rhs) {
				// TODO Auto-generated method stub
				long lrc = getRC(lhs.intValue());
				long rrc = getRC(rhs.intValue());
				if (lrc > rrc)
					return 1;
				else
					return -1;
			}
		}
	}

	/**
	 * Send the message to let uihandler control the active status of situation
	 * on the main activity.
	 * 
	 * @param activateOrUnactivate
	 * @param situationId
	 */
	public static void activateTagOnMainActivity(boolean activateOrUnactivate,
			int situationId) {
		if (null == mUiHandler) {
			Log.e(TAG, "ERROR: mUiHandler is null");
			return;
		}
		if (activateOrUnactivate) {
			/* If it's an activate msg */
			Message msg = mUiHandler.obtainMessage(UIHandler.ACTIVE_TAG,
					situationId);
			mUiHandler.sendMessage(msg);
		} else {
			/* If it's an unactivate msg */
			Message msg = mUiHandler.obtainMessage(UIHandler.UNACTIVE_TAG,
					situationId);
			mUiHandler.sendMessage(msg);
		}
	}

	/**
	 * Send the message to let uihandler toast a customized tip on the main activity.
	 * @param pList
	 */
	public static void showAbductiveProcess(List<Integer> pList) {
		if (null == mUiHandler) {
			Log.e(TAG, "ERROR: mUiHandler is null");
			return;
		}
		if(0 == pList.size()){
			Log.e(TAG, "ERROR: situations list is empty when call showAbductiveProcess(List<Integer> pList. DISCARD.");
			return;
		}
		
		logInfo("Call uiHandler to show the abductive process.");
		
		Message msg = mUiHandler.obtainMessage(
				UIHandler.SHOW_ABDUCTIVE_PROCESS, pList);
		mUiHandler.sendMessage(msg);

	}

	/* Log methods */
	public static void logInfo(String msg) {
		Log.i(TAG, msg);
	}
}
